﻿// <copyright file="VoiceService.cs" company="Ucaya">
// </copyright>
// <author>Ucaya</author>

namespace KinectExtensions.Services
{
    #region

    using System;
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.ComponentModel;
    using System.ComponentModel.Composition;
    using System.IO;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Threading;
    using System.Windows.Input;
    using System.Windows.Threading;

    using KinectExtensions.Collections.ObjectModel;
    using KinectExtensions.Nui;

    using Microsoft.Research.Kinect.Audio;
    using Microsoft.Speech.AudioFormat;
    using Microsoft.Speech.Recognition;

    using DesignerProperties = KinectExtensions.DesignerProperties;

    #endregion

    /// <summary>
    ///   The voice service.
    /// </summary>
    [Export]
    public class VoiceService : INotifyPropertyChanged
    {
        #region Constants and Fields

        /// <summary>
        ///   The recognizer id.
        /// </summary>
        private const string RecognizerId = "SR_MS_en-US_Kinect_10.0";

        /// <summary>
        ///   The words.
        /// </summary>
        private readonly ObservableCollection<IVoiceCommand> voiceCommands;

        /// <summary>
        ///   The listener thread.
        /// </summary>
        private Thread ListenerThread;

        /// <summary>
        ///   The recog info.
        /// </summary>
        private RecognizerInfo RecogInfo;

        /// <summary>
        ///   The recognition engine.
        /// </summary>
        private SpeechRecognitionEngine RecognitionEngine;

        /// <summary>
        ///   The source.
        /// </summary>
        private KinectAudioSource Source;

        private string[] barHintText;

        /// <summary>
        ///   The is enabled.
        /// </summary>
        private bool isEnabled = true;

        /// <summary>
        ///   The listening timer.
        /// </summary>
        private DispatcherTimer listeningTimer;

        /// <summary>
        ///   The ls state.
        /// </summary>
        private ListeningState lsState;

        private Interactions oldInteraction;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        ///   Initializes a new instance of the <see cref = "VoiceService" /> class.
        /// </summary>
        public VoiceService()
        {
            this.InitEngine();
            this.voiceCommands = new ObservableCollection<IVoiceCommand>();
            this.voiceCommands.CollectionChanged += this.voiceCommands_CollectionChanged;
            CommandManager.RequerySuggested += this.CommandManager_RequerySuggested;
            ServiceLocator.Instance.SatisfyImportsOnce(this);
            //this.InteractionManager.CurrentInteractionModeChanged +=
            //    delegate { this.IsEnabled = this.InteractionManager.CurrentInteractionMode == Interactions.Voice; };
        }

        #endregion

        #region Public Events

        /// <summary>
        ///   The property changed.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        #region Public Properties

        public string[] BarHintText
        {
            get
            {
                return this.barHintText;
            }
            private set
            {
                if (this.barHintText != value)
                {
                    this.barHintText = value;
                    this.RaisePropertyChanged(n => n.BarHintText);
                }
            }
        }

        /// <summary>
        ///   Gets or sets InteractionManager.
        /// </summary>
        [Import]
        public InteractionManager InteractionManager { get; set; }

        /// <summary>
        ///   Gets or sets a value indicating whether IsEnabled.
        /// </summary>
        public bool IsEnabled
        {
            get
            {
                return this.isEnabled;
            }

            set
            {
                if (this.isEnabled != value)
                {
                    this.isEnabled = value;
                    this.RaisePropertyChanged(n => n.IsEnabled);
                }
            }
        }

        /// <summary>
        ///   The words.
        /// </summary>
        public ObservableCollection<IVoiceCommand> VoiceCommands
        {
            get
            {
                return this.voiceCommands;
            }
        }

        #endregion

        #region Public Methods

        /// <summary>
        ///   The init engine.
        /// </summary>
        public void InitEngine()
        {
            if (DesignerProperties.IsInDesignTool)
            {
                return;
            }

            this.lsState = ListeningState.Inactive;

            this.listeningTimer = new DispatcherTimer();
            this.listeningTimer.Tick += this.listeningTimer_Tick;
            this.listeningTimer.Interval = new TimeSpan(0, 0, 0, 5, 0);

            try
            {
                this.RecogInfo = SpeechRecognitionEngine.InstalledRecognizers().Where(r => r.Id == RecognizerId).FirstOrDefault();
                this.RecognitionEngine = new SpeechRecognitionEngine(this.RecogInfo.Id);
                this.RecognitionEngine.SpeechDetected += this.RecognitionEngine_SpeechDetected;
                this.RecognitionEngine.SpeechRecognized += this.RecognitionEngine_SpeechRecognized;
                this.RecognitionEngine.SpeechHypothesized += this.RecognitionEngine_SpeechHypothesized;
                this.RecognitionEngine.SpeechRecognitionRejected += this.RecognitionEngine_SpeechRecognitionRejected;
                var gb = new GrammarBuilder("kinect");
                gb.Culture = this.RecogInfo.Culture;
                var g = new Grammar(gb);
                this.RecognitionEngine.LoadGrammar(g);
                this.StartListenerAsync();
            }
            catch (Exception)
            {
                // TODO: Warn user
            }
        }

        /// <summary>
        ///   The start listener.
        /// </summary>
        public void StartListener()
        {
            this.Source = new KinectAudioSource();
            this.Source.SystemMode = SystemMode.OptibeamArrayOnly;
            this.Source.FeatureMode = true;
            this.Source.AutomaticGainControl = false;
            this.Source.MicArrayMode = MicArrayMode.MicArrayAdaptiveBeam;

            Stream kinectStream = this.Source.Start();
            this.RecognitionEngine.SetInputToAudioStream(kinectStream, new SpeechAudioFormatInfo(EncodingFormat.Pcm, 16000, 16, 1, 32000, 2, null));
            this.RecognitionEngine.RecognizeAsync(RecognizeMode.Multiple);
        }

        /// <summary>
        ///   The start listener async.
        /// </summary>
        public void StartListenerAsync()
        {
            this.ListenerThread = new Thread(this.StartListener);
            this.ListenerThread.Name = "Listener";
            this.ListenerThread.Start();
        }

        /// <summary>
        ///   The stop listener.
        /// </summary>
        public void StopListener()
        {
            if (this.RecognitionEngine != null)
            {
                this.RecognitionEngine.RecognizeAsyncCancel();
                this.RecognitionEngine.RecognizeAsyncStop();
                if (this.Source != null)
                {
                    this.Source.Dispose();
                }
            }
        }

        #endregion

        #region Methods

        /// <summary>
        ///   The raise property changed.
        /// </summary>
        /// <param name = "expression">
        ///   The expression.
        /// </param>
        /// <typeparam name = "TProperty">
        /// </typeparam>
        protected void RaisePropertyChanged<TProperty>(Expression<Func<VoiceService, TProperty>> expression)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(this.GetPropertyName(expression)));
            }
        }

        private void CommandManager_RequerySuggested(object sender, EventArgs e)
        {
            this.LoadWordList();
        }

        //private List<IVoiceCommand> lastPluggedCommands = new List<IVoiceCommand>();

        /// <summary>
        ///   The load word list.
        /// </summary>
        /// <param name = "words">
        ///   The words.
        /// </param>
        private void LoadWordList()
        {
            if (DesignerProperties.IsInDesignTool)
            {
                return;
            }

            var choices = new Choices();
            choices.Add("kinect");
            choices.Add("cancel");

            var barHintText = new List<string>();

            foreach (var voiceCommand in this.VoiceCommands)
            {
                if (string.IsNullOrEmpty(voiceCommand.Word) || !voiceCommand.CanExecute(null))
                {
                    continue;
                }

                if (!string.IsNullOrEmpty(voiceCommand.BarHintText))
                {
                    barHintText.Add(voiceCommand.BarHintText);
                }

                choices.Add(voiceCommand.Word);
                choices.Add("kinect " + voiceCommand.Word);
            }

            barHintText.Sort();
            this.BarHintText = barHintText.ToArray();

            var gb = new GrammarBuilder();
            gb.Culture = this.RecogInfo.Culture;

            // gb.Append("kinect");
            gb.Append(choices);

            this.RecognitionEngine.UnloadAllGrammars();
            var g = new Grammar(gb);
            this.RecognitionEngine.UnloadAllGrammars();
            this.RecognitionEngine.LoadGrammar(g);

            // StopListener();
            // StartListenerAsync();
        }

        //void voiceCommand_CanExecuteChanged(object sender, EventArgs e)
        //{
        //    this.LoadWordList();
        //}

        /// <summary>
        ///   The recognition engine_ speech detected.
        /// </summary>
        /// <param name = "sender">
        ///   The sender.
        /// </param>
        /// <param name = "e">
        ///   The e.
        /// </param>
        private void RecognitionEngine_SpeechDetected(object sender, SpeechDetectedEventArgs e)
        {
            //Debug.WriteLine(e.ToString());
        }

        /// <summary>
        ///   The recognition engine_ speech hypothesized.
        /// </summary>
        /// <param name = "sender">
        ///   The sender.
        /// </param>
        /// <param name = "e">
        ///   The e.
        /// </param>
        private void RecognitionEngine_SpeechHypothesized(object sender, SpeechHypothesizedEventArgs e)
        {
            //Debug.WriteLine("Speech Hypotethized");
        }

        /// <summary>
        ///   The recognition engine_ speech recognition rejected.
        /// </summary>
        /// <param name = "sender">
        ///   The sender.
        /// </param>
        /// <param name = "e">
        ///   The e.
        /// </param>
        private void RecognitionEngine_SpeechRecognitionRejected(object sender, SpeechRecognitionRejectedEventArgs e)
        {
            //Debug.WriteLine("Speech Rejected");
        }

        /// <summary>
        ///   The recognition engine_ speech recognized.
        /// </summary>
        /// <param name = "sender">
        ///   The sender.
        /// </param>
        /// <param name = "e">
        ///   The e.
        /// </param>
        private void RecognitionEngine_SpeechRecognized(object sender, SpeechRecognizedEventArgs e)
        {
            if (!this.IsEnabled)
            {
                return;
            }

            //Debug.WriteLine("Speech Recognized");
            if (e.Result.Confidence > 0.9)
            {
                if (this.lsState == ListeningState.Inactive)
                {
                    foreach (var text in e.Result.Words)
                    {
                        if (text.Text == "kinect")
                        {
                            this.lsState = ListeningState.Active;
                            this.listeningTimer.Start();
                            //Debug.WriteLine("kinect Recognized");
                            if (this.InteractionManager.CurrentInteractionMode != Interactions.Kinect)
                            {
                                this.oldInteraction = this.InteractionManager.CurrentInteractionMode;
                                this.InteractionManager.CurrentInteractionMode = Interactions.Voice;
                            }
                        }
                    }
                }

                if (this.lsState == ListeningState.Active)
                {
                    foreach (var text in e.Result.Words)
                    {
                        if (text.Text == "cancel")
                        {
                            this.StopListening();
                            this.InteractionManager.CurrentInteractionMode = this.oldInteraction;
                        }

                        RecognizedWordUnit text1 = text;
                        foreach (var command in this.VoiceCommands.Where(vc => vc.Word == text1.Text))
                        {
                            command.Execute(null, text.Confidence);
                            //Found a command / Restart timer for chaining commands
                            this.StopListening();
                        }
                    }
                }
            }
        }

        private void StopListening()
        {
            this.lsState = ListeningState.Inactive;
            this.listeningTimer.Stop();
            if (this.InteractionManager.CurrentInteractionMode == Interactions.Voice)
            {
                this.InteractionManager.CurrentInteractionMode = this.oldInteraction;
            }
        }

        /// <summary>
        ///   The listening timer_ tick.
        /// </summary>
        /// <param name = "sender">
        ///   The sender.
        /// </param>
        /// <param name = "e">
        ///   The e.
        /// </param>
        private void listeningTimer_Tick(object sender, EventArgs e)
        {
            this.StopListening();
        }

        private void voiceCommands_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            this.LoadWordList();
        }

        #endregion
    }
}